import { useDebounceFn, useEventListener, useMemoizedFn } from 'ahooks';
import { fabric } from 'fabric';
import { cloneDeep, isEmpty, isEqual } from 'lodash-es';
import React, {
  forwardRef,
  useCallback,
  useEffect,
  useImperativeHandle,
  useRef
} from 'react';

import {
  LayerInfoFE,
  LayerInfoFEData,
  ScreenRefType
} from '@/pages/TemplateEditor/interface';
import {
  Arrow,
  getNewLayerInfo,
  initAligningGuidelines,
  isLayerModified,
  needRealTimePreview,
  RoundTriangle,
  ShortLine
} from '@/utils/fabricJs';
import { loadImgFromSvg } from '@/utils/fabricJs/cropImage/controls';
import {
  getCroppedImage,
  getImageBoundByCropped,
  getRotatedPositionByCustomPoint
} from '@/utils/fabricJs/cropImage/result';

import {
  LayerTypeBE,
  LayerTypeFE,
  MIN_LAYER_SIZE_NUM,
  zoomSizes
} from '../../constant';
import { emitter, EVENT_TYPE } from '../../events';
import {
  getNoAnglePoints,
  isImageObject,
  isNotCroppedLayer,
  isShapeObject
} from '../../utils';
import { cropPathId, getCropImageObjects } from './constant';
import styles from './index.module.less';

interface ScreenProps {
  zoomIndex: number;
  layers: LayerInfoFE[];
  activeLayerKey: string;
  cropLayerKey?: string;
  screenSize: string;
  onUpdate: (
    layers: LayerInfoFE[],
    needRealTimePreview?: boolean,
    activeKey?: string,
    needRerender?: boolean,
    needRecordHistory?: boolean
  ) => void;
  changeCropLayer: (key?: string, newLayers?: LayerInfoFE[]) => void;
  setActiveLayerKey: (id: string) => void;
}

const Screen = forwardRef(
  (props: ScreenProps, ref: ScreenRefType): JSX.Element => {
    const {
      layers,
      zoomIndex,
      screenSize,
      cropLayerKey,
      activeLayerKey,
      onUpdate,
      changeCropLayer,
      setActiveLayerKey
    } = props;
    const scale = zoomSizes[zoomIndex] / 100;
    const [width, height] = screenSize.split('x').map(Number);
    const canvasWidth = scale * width;
    const canvasHeight = scale * height;

    const fabricRef = useRef<fabric.Canvas>(null);
    const layersRef = useRef<LayerInfoFE[]>(layers);
    const iconsRef = useRef(null);
    const cropMasksRef = useRef(null);
    const templateScreenRef = useRef(null);

    // 为当前layer数据补充上需要从cavas里获取的数据，例如Shape的content
    const getCurrentLayers = useMemoizedFn(
      (layers: LayerInfoFE[] = []): LayerInfoFE[] => {
        const objects = fabricRef.current.getObjects();

        const shapeContentMap = objects.reduce(
          (acc: Record<string, string>, object) => {
            const data = (object.data || {}) as LayerInfoFEData;
            if (data.type !== LayerTypeBE.Shape) {
              return acc;
            }

            // 先摆正，以及透明度设为1，再获取content
            const originAngle = object.angle;
            const originOpacity = object.opacity;
            object.rotate(0).set({ opacity: 1 });
            const content = object.toDataURL({});
            object.rotate(originAngle).set({ opacity: originOpacity });

            return {
              ...acc,
              [data.id]: content
            };
          },
          {}
        );

        // 补充shape的content信息
        return layers.map((layer) => {
          const content = shapeContentMap[layer.data.id];
          if (!content) {
            return layer;
          }

          const { data } = layer;
          const { graphics } = data;

          return {
            ...layer,
            data: {
              ...data,
              graphics: {
                ...graphics,
                content
              }
            }
          };
        });
      }
    );

    // object的mouseUp事件，其中包含了点击、移动、缩放、旋转处理
    const handleObjectMouseUp = useMemoizedFn(
      (event: fabric.IEvent<MouseEvent>): void => {
        const newLayerObject = event.target;
        // 没有操作项或者处于裁剪时，不做任何处理
        if (
          !newLayerObject?.data?.id ||
          newLayerObject.data.id === cropPathId
        ) {
          if (isEmpty(newLayerObject)) {
            setActiveLayerKey(null);
          }
          return;
        }

        const newLayers = cloneDeep(layersRef.current);
        const oldLayerIndex = newLayers.findIndex(
          ({ data }) => data.id === newLayerObject.data.id
        );
        const oldLayerData = newLayers[oldLayerIndex];

        if (!oldLayerData) {
          return;
        }

        setActiveLayerKey(newLayerObject.data.id);

        // 没有修改layer（即仅仅为点击）
        if (!isLayerModified(oldLayerData, newLayerObject)) {
          return;
        }

        // 以下为修改了layer，即移动、缩放、旋转了layer

        const newInfo = getNewLayerInfo(newLayerObject);

        const newLayerData: LayerInfoFE = {
          ...oldLayerData,
          ...newInfo,
          // newInfo里是已经计算过scale的了，这里设置scale可确保数据正确
          scaleX: 1,
          scaleY: 1,
          data: {
            ...oldLayerData.data,
            rotation:
              oldLayerData.data.rotation + newInfo.angle - oldLayerData.angle
          }
        };

        // 椭圆还需要rx ry两个半径
        if (oldLayerData.type === LayerTypeFE.Ellipse) {
          Object.assign(newLayerData, {
            rx: newLayerData.width / 2,
            ry: newLayerData.height / 2
          });
        }

        newLayers[oldLayerIndex] = newLayerData;

        // 更新右侧layers表单的值（主要为width、height、rotation、position
        emitter.emit(EVENT_TYPE.resetLayerForm, newLayers);

        // 如果是text layer调整大小，需要重新调用RealTimePreview，layer内部的needPreview属性也需要被更改
        const needPreview = needRealTimePreview(oldLayerData, newLayerData);
        if (needPreview) {
          newLayers[oldLayerIndex].data.needPreview = true;
        }

        onUpdate(newLayers, needPreview, newLayerObject.data.id, false);
      }
    );

    // 图片裁剪后的处理
    const handleImageCrop = useCallback(
      async (cropPath, cropLayerKey, isInit = false): Promise<void> => {
        const newLayers = cloneDeep(layersRef.current);
        const cropImageLayerIndex = newLayers.findIndex(
          (layer) => layer.data.id === cropLayerKey
        );
        const cropImageLayer = newLayers[cropImageLayerIndex];

        const canvasObjects = fabricRef.current.getObjects();
        const imgObjectIndex = canvasObjects.findIndex(
          (layer) => layer.data?.id === cropLayerKey
        );
        const imgObject = canvasObjects[imgObjectIndex];
        // 获得除了裁剪对象以外其他对象
        const otherObjects = canvasObjects.filter(
          (layer) => layer.data?.id !== cropLayerKey
        );

        getCroppedImage({
          cropPath,
          imgObject,
          canvas: fabricRef.current,
          canvasScale: scale,
          otherObjects: cropMasksRef.current
            ? [
              cropMasksRef.current.mask,
              cropMasksRef.current.cropPath,
              ...otherObjects
            ]
            : otherObjects,
          callBack: async (newImageJson, cropped) => {
            const newImageLayer = {
              ...cropImageLayer,
              ...newImageJson,
              data: {
                ...cropImageLayer.data,
                image: {
                  ...cropImageLayer.data.image,
                  isCropped: true, // 经过裁剪的标识，方便后续识别处理
                  cropped
                }
              }
            };
            newLayers.splice(cropImageLayerIndex, 1, newImageLayer);

            // 更新右侧layers表单的值（主要为width、height
            emitter.emit(EVENT_TYPE.resetLayerForm, newLayers);

            await onUpdate(
              newLayers,
              false,
              // 初步处理不高亮
              isInit ? null : cropLayerKey,
              false, // 不需要触发重新渲染，后续cancelCropLayer里会触发重新渲染
              // 初步处理不需要历史记录
              !isInit
            );
            changeCropLayer(null, newLayers);
          }
        });
      },
      [scale, onUpdate, changeCropLayer]
    );

    // 进入裁剪状态后的处理，添加crop path层
    const addCroppedLayers = useMemoizedFn(
      (croppedLayer: fabric.Image): void => {
        cropMasksRef.current = getCropImageObjects(
          croppedLayer,
          screenSize,
          loadImgFromSvg(iconsRef.current?.childNodes[0]),
          loadImgFromSvg(iconsRef.current?.childNodes[1]),
          () => changeCropLayer(),
          (eventData, transform) =>
            handleImageCrop(transform.target, cropLayerKey)
        );

        fabricRef.current.add(
          cropMasksRef.current.mask,
          cropMasksRef.current.cropPath
        );
        fabricRef.current.setActiveObject(cropMasksRef.current.cropPath);
      }
    );

    // image render特殊处理，需要填满整个框
    const handleImageLayerRender = (obj: fabric.Image): void => {
      const { width, height } = obj;
      const { width: originalWidth, height: originalHeight } =
        obj.getOriginalSize();

      const scaleX = width / originalWidth;
      const scaleY = height / originalHeight;

      // product图需要按原比例展示
      if (obj.data?.type === LayerTypeBE.Product) {
        const currentScale = Math.min(scaleX, scaleY);
        obj.set({
          width: width / currentScale,
          height: height / currentScale,
          scaleX: currentScale,
          scaleY: currentScale
        });
        obj.setControlsVisibility({
          mb: false,
          ml: false,
          mr: false,
          mt: false
        });
        return;
      }

      // 其余类型的图，需要占满元素
      obj.set({
        width: originalWidth,
        height: originalHeight,
        scaleX,
        scaleY
      });
    };

    // shape render特殊处理，需要考虑边框设置尺寸
    const handleShapeLayerRender = (obj: fabric.Object): void => {
      const { width, height, type } = obj;
      const scaledWidth = obj.getScaledWidth();
      const scaledHeight = obj.getScaledHeight();
      // 根据当前scaledWidth与目标值的差值，计算正确的width
      const currentWidth = width + (width - scaledWidth);
      const currentHeight = height + (height - scaledHeight);
      obj.set({ width: currentWidth, height: currentHeight });
      // 椭圆还需要重新设置rx ry两个半径，才能渲染正确
      if (type === LayerTypeFE.Ellipse) {
        (obj as fabric.Ellipse).set({
          rx: currentWidth / 2,
          ry: currentHeight / 2
        });
      }
    };

    // 页面更新后重新渲染以及初次渲染
    const rerenderScreen = useMemoizedFn(
      // 此处传入activeKey时，优先使用传入的，防止调用此函数时activeKey还未被更新
      (layers: LayerInfoFE[], activeKey = activeLayerKey): void => {
        if (fabricRef.current) {
          fabricRef.current.loadFromJSON(
            {
              backgroundColor: '#fff',
              preserveObjectStacking: true,
              objects: layers
            },
            async () => {
              let cropLayer: fabric.Image; // 当前前端右侧layers编辑器操作要裁剪的layer
              const notCroppedLayers = []; // 编辑时从后端获取但还未经过前端裁剪的layer
              fabricRef.current.getObjects().forEach((obj: fabric.Object) => {
                if (isImageObject(obj)) {
                  const { cropped, originSrc, isCropped } =
                    obj.data.image || {};
                  // 裁剪状态
                  if (obj.data.id === cropLayerKey) {
                    cropLayer = obj;
                    if (isCropped && originSrc) {
                      // 因为setSrc会改变对象的宽高，所以需要提前获取原本属性
                      const originInfo = getNewLayerInfo(obj);
                      // 裁剪状态下需要恢复原始图片
                      obj.setSrc(originSrc, function (obj) {
                        const newProps = getImageBoundByCropped(
                          originInfo,
                          cropped
                        );
                        obj.set(newProps);
                        handleImageLayerRender(obj);
                        fabricRef.current.renderAll();
                      });
                    }
                  }

                  // img层级需要特殊处理一下，因为 img 初始大小其实与后端传过来的w/h不同，会有空框现象
                  handleImageLayerRender(obj);

                  if (isNotCroppedLayer(obj)) {
                    notCroppedLayers.push(obj);
                  }
                }

                if (isShapeObject(obj)) {
                  handleShapeLayerRender(obj);
                }

                if (obj.data?.id === activeKey) {
                  fabricRef.current.setActiveObject(obj);
                }

                if (cropLayerKey) {
                  obj.selectable = false;
                }
              });

              if (cropLayerKey) {
                addCroppedLayers(cropLayer);
                fabricRef.current.moveTo(cropLayer, layers.length - 1); // 截屏时，将图片移动到最前面
                cropLayer.opacity = 1; // 使得图片变得可见
              }

              if (!isEmpty(notCroppedLayers)) {
                notCroppedLayers.forEach(
                  async (notCroppedLayer: fabric.Image) => {
                    const imageWidth = notCroppedLayer.getScaledWidth();
                    const imageHeight = notCroppedLayer.getScaledHeight();
                    const [
                      topCropped,
                      rightCropped,
                      bottomCropped,
                      leftCropped
                    ] = notCroppedLayer.data.image.cropped;

                    const cropWidth =
                      ((100 - leftCropped - rightCropped) * imageWidth) / 100;
                    const cropHeight =
                      ((100 - topCropped - bottomCropped) * imageHeight) / 100;

                    const {
                      center: imageCenterPoint,
                      leftTop: imageLeftTopPoint
                    } = getNoAnglePoints(notCroppedLayer);

                    // 摆正时的crop中心坐标点
                    const noAngleCropCenterPoint = new fabric.Point(
                      imageLeftTopPoint.x +
                      (imageWidth * leftCropped) / 100 +
                      cropWidth / 2,
                      imageLeftTopPoint.y +
                      (imageHeight * topCropped) / 100 +
                      cropHeight / 2
                    );

                    // 绕image中心旋转得到正确的crop中心坐标
                    const currentCropCenterPoint =
                      getRotatedPositionByCustomPoint(
                        imageCenterPoint,
                        noAngleCropCenterPoint,
                        -notCroppedLayer.angle
                      );

                    const cropPath = new fabric.Rect({
                      left: currentCropCenterPoint.x,
                      top: currentCropCenterPoint.y,
                      width: cropWidth,
                      height: cropHeight,
                      angle: notCroppedLayer.angle,
                      type: LayerTypeFE.Rect,
                      inverted: true,
                      strokeWidth: 0,
                      strokeUniform: true
                    });

                    await handleImageCrop(
                      cropPath,
                      notCroppedLayer.data.id,
                      true
                    );
                  }
                );
              }

              fabricRef.current.renderAll();
            }
          );
        }
      }
    );

    // layer更新数据后，更新相应object，并渲染
    const rerenderLayer = useMemoizedFn((layer: LayerInfoFE): void => {
      if (!fabricRef.current || !layer?.data?.id) {
        return;
      }

      const obj = fabricRef.current
        .getObjects()
        .find((o) => o.data?.id === layer.data.id);

      if (!obj) {
        return;
      }

      const render = (): void => {
        // 外部的layer数据里的width和height都是已经计算了scale的，所以这里scale需要设置为1
        obj.set({ ...layer, scaleX: 1, scaleY: 1 });

        if (isShapeObject(obj)) {
          handleShapeLayerRender(obj);
        }

        if (isImageObject(obj)) {
          handleImageLayerRender(obj);
        }

        // 需要调用setCoords重新计算信息，否则可能会不渲染
        obj.setCoords();
        fabricRef.current.requestRenderAll();
      };

      // 图片重新上传后，需要通过setSrc方法重新加载
      if (
        isImageObject(obj) &&
        obj.getSrc() !== new URL(layer.src, window.location.origin).toString()
      ) {
        obj.setSrc(layer.src, render);
        return;
      }

      render();
    });

    useEffect(() => {
      // 幕布缩放、改变尺寸
      if (fabricRef.current) {
        fabricRef.current.setHeight(canvasHeight);
        fabricRef.current.setWidth(canvasWidth);
        fabricRef.current.setZoom(scale);

        return;
      }

      // canvas初始化以及绑定辅助线、特殊object初始化
      fabric.RoundTriangle = RoundTriangle;
      fabric.RoundTriangle.fromObject = (object, callback): any => {
        return fabric.Object._fromObject('RoundTriangle', object, callback);
      };

      fabric.ShortLine = ShortLine;
      fabric.ShortLine.fromObject = (object, callback): any => {
        return fabric.Object._fromObject('ShortLine', object, callback);
      };

      fabric.Arrow = Arrow;
      fabric.Arrow.fromObject = (object, callback): any => {
        return fabric.Object._fromObject('Arrow', object, callback);
      };

      fabricRef.current = new fabric.Canvas('templateScreen', {
        backgroundColor: '#fff',
        preserveObjectStacking: true,
        width: canvasWidth,
        height: canvasHeight,
        selection: false
      });
      fabricRef.current.setZoom(scale);
      fabricRef.current.on('mouse:up', handleObjectMouseUp);
      fabricRef.current.on('mouse:down', (e) => {
        const object = e.target;
        if (!object) {
          return;
        }

        // 先去除缩小限制，以便后面缩小过程添加缩放限制
        object.set({ minScaleLimit: 0 });

        // 裁剪框保持高亮状态
        if (
          object.data?.id === cropPathId ||
          object.clipPath?.data?.id === cropPathId
        ) {
          const cropPath =
            object.data?.id === cropPathId ? object : object.clipPath;
          fabricRef.current.setActiveObject(cropPath);
        }
      });
      fabricRef.current.on('object:scaling', (e) => {
        // 缩放过程中，设置最小缩放比例
        const object = e.target;
        if (!object || object.minScaleLimit) {
          return;
        }

        const scaledWidth = object.getScaledWidth();
        const diffWidth = scaledWidth - object.width * object.scaleX;
        // 宽度的最小缩放比例
        const minScaleLimitX = (MIN_LAYER_SIZE_NUM - diffWidth) / object.width;

        const scaledHeight = object.getScaledHeight();
        const diffHeight = scaledHeight - object.height * object.scaleY;
        // 高度的最小缩放比例
        const minScaleLimitY =
          (MIN_LAYER_SIZE_NUM - diffHeight) / object.height;

        // 根据当前缩放操作，设置最小缩放比例
        switch (e.transform?.action) {
          case 'scaleX':
            object.set({ minScaleLimit: minScaleLimitX });
            return;
          case 'scaleY':
            object.set({ minScaleLimit: minScaleLimitY });
            return;
          case 'scale':
            object.set({
              minScaleLimit: Math.max(minScaleLimitX, minScaleLimitY)
            });
            return;
          default:
            return;
        }
      });
      initAligningGuidelines(fabricRef.current);
    }, [canvasWidth, canvasHeight, scale, handleObjectMouseUp]);

    // 存储最新的layers数据
    useEffect(() => {
      if (!isEqual(layers, layersRef.current)) {
        layersRef.current = layers;
      }
    }, [layers]);

    // 需要传递给外部使用的函数
    useImperativeHandle(ref, () => ({
      getCurrentLayers,
      rerenderScreen,
      rerenderLayer
    }));

    // 裁剪时，点击画布外，取消裁剪
    useEventListener(
      'mousedown',
      (e) => {
        if (
          cropLayerKey &&
          templateScreenRef.current &&
          !templateScreenRef.current.contains(e.target)
        ) {
          e.stopPropagation(); // 点击画布以外区域，阻止事件向下传递；点击画布以内，不能阻止向下传递
          changeCropLayer();
        }
      },
      { capture: true }
    );

    const { run: onResize } = useDebounceFn(
      () => {
        fabric.devicePixelRatio =
          fabric.window.devicePixelRatio ||
          fabric.window.webkitDevicePixelRatio ||
          fabric.window.mozDevicePixelRatio ||
          1;
        fabricRef.current.setDimensions({
          width: canvasWidth,
          height: canvasHeight
        });
        fabricRef.current.requestRenderAll();
      },
      { wait: 300 }
    );
    useEventListener('resize', onResize);

    return (
      <div className={styles.wrapper}>
        <div ref={iconsRef} className="hidden">
          确定
        </div>
        <div
          className={styles['canvas-wrapper']}
          style={{
            minHeight: `${canvasHeight}px`,
            minWidth: `${canvasWidth}px`
          }}
        >
          <div
            style={{ width: canvasWidth, height: canvasHeight }}
            // 会新生成一个画布，并非当前id="templateScreen"的画布，因此获取画布容器需要在外层加个div
            ref={templateScreenRef}
          >
            <canvas
              id="templateScreen"
              width={canvasWidth}
              height={canvasHeight}
            />
          </div>
        </div>
      </div>
    );
  }
);

export default Screen;
